/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2016-06-30
 * V4.0
 */
package com.jphenix.servlet.tm;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jphenix.servlet.filter.BaseFilter;
import com.jphenix.servlet.parent.ServiceBeanParent;
import com.jphenix.share.lang.SLong;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Register;
import com.jphenix.standard.docs.Running;
import com.jphenix.standard.servlet.IFilter;
import com.jphenix.standard.servlet.IRequestManager;
import com.jphenix.standard.servlet.IResponseManager;
import com.jphenix.standard.servlet.api.IFilterConfig;

/**
 * 任务调度过滤器
 * 
 * 原理：
 * 
 * 			在页面中使用JavaScript不断的访问调度URL，并将主键（key）作为参数传入
 * 
 * 			调用动作：
 * 
 * 				/task.ha?key=[主键]&ts=[首次调用时为空，后期传入上次获取到的时间戳]
 * 
 * 			返回的内容:
 * 
 * 					_taskInfo = {'_ts':'时间戳':'_intervaltime':'轮询间隔时间','参数主键':'参数值'}
 * 
 * 					需要在js中定义好 _taskInfo 变量：var _taskInfo;   调用动作路径后处理返回值： eval(res);  调用后 _taskInfo 中的值就被更新了
 * 
 * 					取值方法：_taskInfo['_ts'] 时间戳，如果为空，说明数据库中没有 pagecode 对应的记录
 * 
 * 					获取到的_ts值与上一次的ts值做比较，如果不同，说明有新的调度任务，可以_taskInfo['参数主键'] 获取调度任务设置的值
 * 
 * 2019-01-24 修改了父类的类路径
 * 2020-03-13 修改了从URL中获取参数
 * 2022-09-04 隔离了ServletApi，兼容新老Tomcat
 * 
 * 
 * @author 马宝刚
 * 2012-9-22 下午8:56:13
 * 
 * com.jphenix.servlet.tm.TaskManagerFilter
 */
@ClassInfo({"2022-09-07 13:09","任务调度过滤器"})
@BeanInfo({"taskmanagerfilter"})
@Register({"filtervector"})
@Running({"99"})
public class TaskManagerFilter extends ServiceBeanParent implements IFilter {
	
	/**
	 * 启动后执行的方法
	 */
	final static String AFTER_START_METHOD = "init";
	
	protected CheckTimeOut             cto             = null;                               //检测会话超时线程
	protected Map<String,TaskAroundVO> taskMap         = new HashMap<String,TaskAroundVO>(); //任务信息容器
	protected String                   servletPath     = "/task.ha";						 //设置调度动作路径
	protected String                   encoding        = "UTF-8";							 //输出编码格式
	protected int                      sessionTimeOut  = 0;                                  //会话超时时间（分）
	protected Map<String,Integer>      taskSizeMap     = new HashMap<String,Integer>();      //被允许的任务主键，以及缓存深度
	protected Map<String,Integer>      taskIntervalMap = new HashMap<String,Integer>();      //被允许的任务主键，以及轮询间隔时间（毫秒）
	
	//用来做是否已登录的判断
	protected Map<String,String> loginSessionKeyMap    = new HashMap<String,String>();       //获取需要从会话中获取布尔值的主键
	
	/**
	 * 检测推送消息等待超时线程
	 * @author 马宝刚
	 */
	protected class CheckTimeOut extends Thread {
	    
	    /**
	     * 构造函数
	     */
	    public CheckTimeOut() {
	        super("TaskManagerFilter-CheckTimeOut");
	    }
	    
	    /**
	     * 覆盖方法
	     */
	    @Override
        public void run() {
	        //获取任务主键序列
	        List<String> keyList;
	        TaskAroundVO taVO; //任务信息容器
	        long nowTime; //当前时间
	        while(true) {
	            nowTime = System.currentTimeMillis();
	            keyList = BaseUtil.getMapKeyList(taskMap);
	            for(String key:keyList) {
	                taVO = taskMap.get(key);
	                if(taVO.lastPushTime+(sessionTimeOut*60*1000)<nowTime) {
	                    taskMap.remove(key);
	                }
	            }
	            try {
	                Thread.sleep(60000);
	            }catch(Exception e) {}
	        }
	    }
	}
	
	/**
	 * 构造函数
	 * @author 马宝刚
	 */
	public TaskManagerFilter() {
		super();
	}

	/**
	 * 覆盖方法
	 * 马宝刚
	 * 2012-9-22 下午8:56:13
	 */
	@Override
    public int getIndex() {
		return 4;
	}

	/**
	 * 覆盖方法
	 * 
	 * 传入参数：
	 * 
	 * key      任务主键（不同功能的任务信息主键）
	 * xml      是否返回xml格式（默认Json格式）
	 * multi    是否返回多条信息记录（默认返回一条）
	 * 
	 * 马宝刚
	 * 2012-9-22 下午8:56:13
	 */
	@Override
    public boolean doFilter(IRequestManager req, IResponseManager resp) throws Exception {
		//System.out.println("========["+req.getServletPath()+"]");
		if(!req.getServletPath().equals(servletPath)) {
			return false;
		}
		//是否返回xml格式
		boolean isXmlStyle = boo(req.getUrlParameter("xml"));
		if(isXmlStyle) {
			resp.setHeader("Content-Type","text/xml; charset=UTF-8"); 
		}else {
			resp.setHeader("Content-Type","application/json; charset=UTF-8"); 
		}
		//主键
		String key = req.getUrlParameter("key");
		if(key==null || key.length()<1) {
			return false;
		}
		if(!taskSizeMap.containsKey(key)) {
			//后台没有推送该消息，该请求不被允许
			return false;
		}
		
		if(loginSessionKeyMap.containsKey(key)) {
			//如果存在该值，从会话中获取该主键值，如果为true，说明已经通过登录验证
			if(req.iGetSession()==null 
					|| !boo(req.iGetSession().getAttribute(loginSessionKeyMap.get(key)))) {
				//需要在动作类中设置会话值   为 1
				if(isXmlStyle) {
					resp.iGetOutputStream().write(("<task_info><status>-99</status></task_info>").getBytes());
				}else {
					resp.iGetOutputStream().write(("var _taskInfo = {'status':'-99'}").getBytes());
				}
				return true;
			}
		}
		
		//时间戳
		String ts = req.getUrlParameter("ts");
		if(ts==null || ts.length()<1) {
		    ts = String.valueOf(System.currentTimeMillis());
		}
		resp.addHeader("Cache-Control","no-cache");
		resp.addHeader("Pragma","no-cache");
		//获取对应的信息容器
		TaskAroundVO taVO = taskMap.get(key);
		if(taVO==null) {
			
			//缓存深度
			int bufferSize = taskSizeMap.get(key);
			if(bufferSize<1) {
				bufferSize = 100;
			}
			
			//轮询间隔时间
			int intervalTime = taskIntervalMap.get(key);
			if(intervalTime<1) {
				intervalTime = 1000;
			}
			//在发起请求的时候才构建这个VO
			taVO = new TaskAroundVO(this,key,intervalTime,bufferSize);
			taskMap.put(key,taVO);
			
			if(isXmlStyle) {
				resp.iGetOutputStream().write(("<task_info><status>1</status><ts>"+ts+"</ts><interval_time>"+intervalTime+"</interval_time></task_info>").getBytes());
			}else {
				resp.iGetOutputStream().write(("var _taskInfo = {'status':'1','_ts':'"+ts+"','_intervaltime':'"+intervalTime+"'}").getBytes());
			}
		}else {
			if(boo(req.getUrlParameter("debug"))) {
				resp.iGetOutputStream().write(taVO.toString().getBytes(encoding));
				return true;
			}
		    String res = taVO.getTask(SLong.valueOf(ts),isXmlStyle);
			resp.iGetOutputStream().write(res.getBytes(encoding));
		}
		resp.flushBuffer();
		return true;
	}
	
	/**
	 * 执行初始化
	 * @param bf         过滤器管理类
	 * @param config     Servlet配置信息类
	 * @throws Exception 异常（如果初始化发生异常，则放弃不再使用）
	 * 2019年6月15日
	 * @author MBG
	 */
	@Override
	public void init(BaseFilter bf, IFilterConfig config) throws Exception {
		//推送任务超时时间
		sessionTimeOut = sint(prop.getParameter("task_service/session_time_out"));
		if(sessionTimeOut<1) {
		    sessionTimeOut = 60;
		}
		//启动会话超时检查线程
		cto = new CheckTimeOut();
		cto.start();
	}
	
	
	/**
	 * 数据推送服务首先执行该方法设置缓存深度和轮询间隔时间
	 * @param key 任务主键
	 * @param bufferSize 缓存深度
	 * @param intervalTime 轮询间隔时间（毫秒）
	 * @param loginSessionKey 如果有值的话，用来从会话中获取是否已登录的判断
	 * 2016年7月4日
	 * @author MBG
	 */
	public void initTask(String key,int bufferSize,int intervalTime,String loginSessionKey) {
		taskSizeMap.put(key,bufferSize);
		taskIntervalMap.put(key,intervalTime);
		if(loginSessionKey!=null && loginSessionKey.length()>0) {
			loginSessionKeyMap.put(key,loginSessionKey);
		}
	}


	/**
	 * 放入数据
	 * @param key 任务主键
	 * @param infoMap 任务数据容器（value 只能是类似字符串的变量，不能是Map之类的）
	 * 2016年7月4日
	 * @author MBG
	 */
	public void updateTask(String key, Map<String,?> infoMap) {
		//获取对应的信息容器
		TaskAroundVO taVO = taskMap.get(key);
		if(taVO!=null) {
			//只有发起获取任务信息请求的时候，才会构造该信息VO
			taVO.addTask(infoMap);
		}
	}
	
	/**
	 * 放入数据
	 * @param key 任务主键
	 * @param content 数据字符串
	 * 2016年7月4日
	 * @author MBG
	 */
	public void updateTask(String key,String content) {
		//获取对应的信息容器
		TaskAroundVO taVO = taskMap.get(key);
		if(taVO!=null) {
			//只有发起获取任务信息请求的时候，才会构造该信息VO
			taVO.addTask(content);
		}
	}

	/**
	 * 覆盖方法
	 */
    @Override
    public String getFilterActionExtName() {
        return "ha";
    }
    
    /**
     * 获取指定任务信息容器（注意：该方法仅用于调试，查看容器信息数量等）
     * @param key 任务主键
     * @return    对应的任务信息容器
     * 2018年1月25日
     * @author MBG
     */
    public TaskAroundVO getVO(String key) {
    	return taskMap.get(key);
    }

	/**
	 * 覆盖方法
	 */
	@Override
	public void destroy() {}
}
